Type Network

	Const NET_ALLOW_LOOPBACK% = False
	
	Const MAX_IO_BUFFER_SIZE% = 64 * 1024
	
	Const MAX_ENTITY_STATE_SIZE%	= 64
		
	Const PPM_ORDER%	      = 6
	Const PPM_ALLOCATOR_SIZE% = 8
	
	Const PORT_NUM%  = 51381
	Const APP_MAGIC% = $E7A2
	
	Const EVENT_ENTITY_UPDATE%  = 0
	Const EVENT_ENTITY_CONTROL% = 1
	Const EVENT_SERVER_CLOSED%  = 2
	Const EVENT_SERVER_ACCEPT%  = 3
	Const EVENT_LEAVE%          = 4
	Const EVENT_QUERY%		    = 5
	Const EVENT_ANNOUNCE%       = 6
	Const EVENT_JOIN%           = 7
	Const EVENT_CHAT%           = 8
	
	Const STATE_OFFLINE%    = 0
	Const STATE_SERVER%     = 1
	Const STATE_CLIENT%     = 2
	Const STATE_CONNECTING% = 3
	
	Const UPDATE_INTERVAL%  = 33
	Const TIMEOUT_DURATION% = 10000
	
	Const DEFAULT_MAX_PLAYERS% = 8
	
	Global lastSentTime%
	Global lastStateTime%
	
	Global totalBytesIn%			
	Global totalBytesOut%
	Global totalPacketsIn%        
	Global totalPacketsOut%      
	Global totalPacketsOutDropped%
	Global totalPacketsInDropped%
	
	Global ppmInputBuffer:RAMStream  = Null
	Global ppmOutputBuffer:RAMStream = Null
	
	Global broadcastPackets:TList = New TList
	
	Global clientList:TList = New TList
	
	Global netState% = STATE_OFFLINE
	
	Global broadcastIP% = HostIp("255.255.255.255")
	Global localIPs%[]
	
	Global playerID%
	Global maxPlayers%
	
	Global socket:TSocket = CreateUDPSocket()
	
	Function Init%()
	
		ppmInputBuffer  = RAMStream.Create(MAX_IO_BUFFER_SIZE, False)
		ppmOutputBuffer = RAMStream.Create(MAX_IO_BUFFER_SIZE, False)
		
		If Not PPM_init(PPM_ALLOCATOR_SIZE)
		
			Console.ConsolePrint("Error intializing PPMd compression engine")
			
			Return False
			
		EndIf
		
		If Not Socket.Bind(PORT_NUM)
		
			Console.ConsolePrint("Error binding socket to UDP port " + String(PORT_NUM))
			
			Return False
			
		EndIf
		
		playerID   = 0
		maxPlayers = 0
		
		localIPs = HostIps("")
		
		clientList.Clear()
		broadcastPackets.Clear()
		
		lastSentTime  = 0
		lastStateTime = 0
		
		totalBytesIn			= 0     
		totalBytesOut          	= 0
		totalPacketsIn         	= 0
		totalPacketsOut        	= 0
		totalPacketsOutDropped 	= 0
		totalPacketsInDropped  	= 0
		
		SendAnnounce(broadcastIP)
		
		RefreshGameList()
		
		Return True
		
	End Function
	
	Function Update%()
		
		ReceiveMessages()
		
		If Abs(System.frameTime - lastSentTime) > UPDATE_INTERVAL
		
			lastSentTime = System.frameTime
			
			SendMessages()
			
		EndIf
		
		If netState = STATE_CONNECTING
		
			If Abs(system.frameTime - lastStateTime) > TIMEOUT_DURATION
			
				Console.ConsolePrint("Connection request to server has timed out")
				
				LeaveGame()
				
			EndIf
			
		EndIf
		
		For Local client:NetClient = EachIn clientList
		
			If Abs(System.frameTime - client.lastReceivedTime) > TIMEOUT_DURATION
			
				If netState = STATE_CLIENT
				
					Console.ConsolePrint("Disconnected - Timed out waiting for server")
					Console.ConsolePrintClientInfo(client)	
										
					LeaveGame()
					
					Exit
					
				Else If netState = STATE_SERVER
				
					Console.ConsolePrint("Client disconnected - Timed out waiting for response")
					Console.ConsolePrintClientInfo(client)	
												
					DisconnectClient(client)
					
				EndIf
				
			EndIf
		
		Next
		
		Return True
		
	End Function
	
	Function Shutdown%()
	
		socket.Close()
		
		clientList.Clear()
		
		ppmInputBuffer.FreeBuffer()
		ppmOutputBuffer.FreeBuffer()
		
		PPM_Shutdown()
		
		Return True
		
	End Function
		
	Function SendMessages()
		
		If clientList.Count() > 0 Then lastStateTime = System.frameTime
		
		For Local broadcastPacket:NetPacket = EachIn broadcastPackets
		
			broadcastPacket.SendTo(Null)
			
		Next
		
		broadcastPackets.Clear()
				
		For Local client:NetClient = EachIn clientList
			
			If client.receivedPackets.Count() > 0
			
				client.sendPacket.ackedSeq = NetPacket(client.receivedPackets.Last()).thisSeq
				
			EndIf
			
			client.sendPacket.SendTo(client)
			client.sentPackets.AddLast(client.sendPacket)
			
			Local newPacket:NetPacket = New NetPacket
			
			newPacket.thisSeq  = client.sendPacket.thisSeq + 1
			newPacket.ackedSeq = client.sendPacket.ackedSeq

			If client.receivedPackets.Count() > 0
			
				client.sendPacket.diffSeq = NetPacket(client.receivedPackets.Last()).ackedSeq
				
			EndIf
			
			client.sendPacket = newPacket
	
		Next

	End Function
	
	Function ReceiveMessages()
	
		Local bytesReceived%  = socket.ReadAvail()
		Local bytesProcessed% = 0
		
		While bytesProcessed < bytesReceived
		
			Local sourceIP%   = 0
			Local sourcePort% = 0
			
			Local msgBytes% = socket.recvFrom(ppmInputBuffer.buffer, MAX_IO_BUFFER_SIZE, sourceIP, sourcePort)
			
			bytesProcessed = bytesProcessed + msgBytes
			
			Local fromSelf% = False
			
			For Local LocalIp% = EachIn localIPs
				
				If LocalIp = sourceIP
				
					fromSelf = True
					Exit
					
				EndIf
				
			Next
			
			If sourcePort = PORT_NUM And (fromSelf = False Or NET_ALLOW_LOOPBACK)
				
				ppmInputBuffer.SetSize(msgBytes)
				ppmInputBuffer.Seek(0)
				
				Local magic% = ppmInputBuffer.ReadShort()
				
				If magic = APP_MAGIC
				
					Local crc% = ppmInputBuffer.ReadInt()
					
					If crc = ppmInputBuffer.Checksum()
					
						ppmOutputBuffer.SetSize(ppmOutputBuffer.capacity)
						ppmOutputBuffer.Seek(0)
										
						PPM_Decode(ppmOutputBuffer, ppmInputBuffer, PPM_ORDER, 1)
						
						ppmOutputBuffer.SetSize(ppmOutputBuffer.position)
						ppmOutputBuffer.Seek(0)
						
						Local clientFound% = False
						
						For Local client:NetClient = EachIn clientList
						
							If sourceIP = client.IP
							
								clientFound = True
								
								Local result% = ProcessMessage(client, ppmOutputBuffer, sourceIP)
								
								If result = 1
									
									client.receivedPackets.AddLast(client.lastPacket)
							
									client.bytesIn = client.bytesIn + msgBytes
									client.packetsIn = client.packetsIn + 1
								
									totalBytesIn   = totalBytesIn + msgBytes
									totalPacketsIn = totalPacketsIn + 1
									
								Else If result = 0
						
									client.packetsInDropped = client.packetsInDropped + 1
									totalPacketsInDropped   = totalPacketsInDropped + 1
									
								Else If result = -1
				
									client.bytesIn = client.bytesIn + msgBytes
									client.packetsIn = client.packetsIn + 1
								
									totalBytesIn   = totalBytesIn + msgBytes
									totalPacketsIn = totalPacketsIn + 1
									
								EndIf									
								
								client.lastPacket = New NetPacket
													
								Exit
								
							EndIf
						
						Next
						
						If Not clientFound
							
							Local ret% = ProcessMessage(Null, ppmOutputBuffer, sourceIP)
							
							If ret = 1 Or ret = -1
							
								totalBytesIn   = totalBytesIn + msgBytes
								totalPacketsIn = totalPacketsIn + 1
								
							Else
							
								totalPacketsInDropped = totalPacketsInDropped + 1
								
							EndIf
							
						EndIf
							
					Else
					
						totalPacketsInDropped = totalPacketsInDropped + 1
						
						For Local client:NetClient = EachIn clientList
						
							If sourceIP = client.IP
							
								client.packetsInDropped = client.packetsInDropped + 1
								
								Exit
								
							EndIf
							
						Next
						
					EndIf
					
				EndIf
	
			EndIf
			
		Wend
		
	End Function
	
	Function ProcessMessage%(client:NetClient, messageData:RAMStream, sourceIP%)
						
		Local diffSeq%      = messageData.ReadShort()	
		Local receivedSeq%  = messageData.ReadShort()
		Local lastAckedSeq% = messageData.ReadShort()
		
		Local diffPacket:NetPacket = Null
		
		If client <> Null

			If (diffSeq = 0 And receivedSeq = 0 And lastAckedSeq = 0) And client.packetsIn > 0
			
				client.lastReceivedTime = System.frameTime
				
			Else
		
				client.lastPacket.ackedSeq = lastAckedSeq
				client.lastPacket.thisSeq  = receivedSeq
				client.lastPacket.diffSeq  = diffSeq
				
				client.lastReceivedTime = System.frameTime
	
				For Local searchPacket:NetPacket = EachIn client.sentPackets
				
					If searchPacket.thisSeq < lastAckedSeq
					
						client.sentPackets.Remove(searchPacket)
						
					EndIf
					
				Next
									
				For Local searchPacket:NetPacket = EachIn client.receivedPackets
				
					If searchPacket.thisSeq = diffSeq
					
						diffPacket = searchPacket
						
					Else If searchPacket.thisSeq < diffSeq
					
						client.receivedPackets.Remove(searchPacket)
						
					EndIf
					
				Next
				
				If diffPacket = Null And client.packetsIn > 0
									
					Return False
				
				EndIf
				
			EndIf
			
		Else
		
			If receivedSeq Or lastAckedSeq Or diffSeq
				
				Return False
			
			EndIf
			
		EndIf	
			
		While Not messageData.Eof()
		
			Local eventType% = messageData.ReadByte()
			
			Local event:NetEvent = Null
			
			Select eventType
			
				Case EVENT_CHAT
					
					event = New NetChatEvent
										
				Case EVENT_ANNOUNCE
					
					event = New NetAnnounceEvent

				Case EVENT_QUERY
				
					event = New NetQueryEvent
					
				Case EVENT_JOIN
					
					event = New NetJoinEvent
					
				Case EVENT_ENTITY_UPDATE
				
					event = New NetStateEvent
					
				Case EVENT_ENTITY_CONTROL
				
					event = New NetControlEvent
					
				Case EVENT_LEAVE
				
					event = New NetLeaveEvent
					
				Case EVENT_SERVER_CLOSED
					
					event = New NetServerClosedEvent
					
				Case EVENT_SERVER_ACCEPT
				
					event = New NetServerAcceptEVent
										
			End Select
			
			If event
			
				Event.Deserialize(messageData, client, sourceIP, diffPacket)
			
				If client
				
					client.lastPacket.events.AddLast(event)
					
				EndIf
				
			Else
			
				Return False
				
			EndIf
				
		Wend
		
		If (diffSeq = 0 And receivedSeq = 0 And lastAckedSeq = 0)
		
			If client
			
				If client.packetsIn > 0
		
					Return -1
					
				EndIf
				
			EndIf
			
		EndIf
		
		Return True
		
	End Function
	
	Function HostGame%(_saveName$, _maxPlayers% = DEFAULT_MAX_PLAYERS)
		
		netState = STATE_SERVER
		
		playerID   = 0
		Network.maxPlayers = _maxPlayers
		
		If Not Game.LoadGame(_saveName$)
		
			netState = STATE_OFFLINE
			
			lastStateTime = 0
			
			Console.consoleOpen = True
			
			Return False
			
		EndIf
		
		SendAnnounce(broadcastIP)
		
		Console.consoleOpen = False
		
		Return True
		
	End Function	
	
	Function JoinGame(_hostName$)
	
		Local joinEvent:NetJoinEvent = New NetJoinEvent
		Local joinPacket:NetPacket   = New NetPacket

		joinEvent.playerName$ = Game.playerName$
		joinPacket.singleIP = HostIp(_hostName$)
		
		joinPacket.events.AddLast(joinEvent)
		broadcastPackets.AddLast(joinPacket)
		
		netState = STATE_CONNECTING
		
		Network.lastStateTime = System.frameTime
					
	End Function
	
	Function LeaveGame()
	
		If netState = STATE_SERVER
		
			For Local client:NetClient = EachIn clientList
			
				DisconnectClient(client)
				
			Next
			
		Else If netState = STATE_CLIENT
		
			Local leaveEvent:NetLeaveEvent = New NetLeaveEvent
			Local leavePacket:NetPacket    = New NetPacket
	
			Local server:NetClient = NetClient(clientList.First())
			
			leavePacket.singleIP = server.IP
			
			leavePacket.events.AddLast(leaveEvent)
			broadcastPackets.AddLast(leavePacket)

		EndIf
		
		clientList.Clear()
		
		Game.EndGame()
		
		netState = STATE_OFFLINE
		
		lastStateTime = 0
		
		playerID   = 0
		maxPlayers = 0
		
		SendAnnounce(broadcastIP)
		
		Console.consoleOpen = True
		
	End Function
	
	Function DisconnectClient(client:NetClient, clientIP% = 0)
		
		If client
		
			If clientIP = 0 Then clientIP = client.IP
			
			Game.RemovePlayer(client.playerID)
			clientList.Remove(client)
			
			SendChat("Server", "'" + client.playerName$ + "' has left the game")
			
		EndIf
		
		Local serverClosedEvent:NetServerClosedEvent = New NetServerClosedEvent
		Local serverClosedPacket:NetPacket           = New NetPacket

		serverClosedPacket.singleIP = clientIP
		
		serverClosedPacket.events.AddLast(serverClosedEvent)
		broadcastPackets.AddLast(serverClosedPacket)
	
	End Function
		
	Function SendAnnounce(destinationIP%)
		
		Local announceEvent:NetAnnounceEvent = New NetAnnounceEvent 

		announceEvent.playerName$ = Game.playerName$
				
		If netState = STATE_SERVER
		
			announceEvent.numPlayers = clientList.Count() + 1
			announceEvent.maxPlayers = maxPlayers
			
			announceEvent.gameName$ = Game.SaveName$
			
		Else
		
			announceEvent.numPlayers = 0
			
		EndIf
		
		Local announcePacket:NetPacket = New NetPacket

		announcePacket.events.AddLast(announceEvent)
		broadcastPackets.AddLast(announcePacket)
	
	End Function

	Function RefreshGameList()
		
		Local queryEvent:NetQueryEvent = New NetQueryEvent
		Local queryPacket:NetPacket = New NetPacket

		queryPacket.events.AddLast(queryEvent)
		broadcastPackets.AddLast(queryPacket)
		
	End Function
	
	Function SendChat(playerName$, text$, lan% = False, client:NetClient = Null)
		
		If netState = STATE_OFFLINE Or netState = STATE_CONNECTING Or lan = True
		
			Console.ConsolePrint(playerName$ + " (lan): " + text$, Console.CONSOLE_CHAT_COLOR_R, Console.CONSOLE_CHAT_COLOR_G, Console.CONSOLE_CHAT_COLOR_B)
			
			Local chatEvent:NetChatEvent = New NetChatEvent
			
			chatEvent.playerName$ = Game.playerName$
			chatEvent.message$    = text$
			
			Local chatPacket:NetPacket = New NetPacket

			chatPacket.events.AddLast(chatEvent)
			
			broadcastPackets.AddLast(chatPacket)
					
		Else

			Console.ConsolePrint(playerName$ + ": " + text$, Console.CONSOLE_CHAT_COLOR_R, Console.CONSOLE_CHAT_COLOR_G, Console.CONSOLE_CHAT_COLOR_B)
			
			If netState = STATE_CLIENT
			
				Local chatEvent:NetChatEvent = New NetChatEvent
				
				chatEvent.playerName$ = playerName$
				chatEvent.message$    = text$
				
				Local server:NetClient = NetClient(clientList.First())
				server.sendPacket.events.AddLast(chatEvent)
				
			Else If netState = STATE_SERVER

				Local chatEvent:NetChatEvent = New NetChatEvent
				
				chatEvent.playerName$ = playerName$
				chatEvent.message$    = text$
						
				For Local relayClient:NetClient = EachIn clientList
					
					If client <> relayClient Then relayClient.sendPacket.events.AddLast(chatEvent)
					
				Next		
				
			EndIf
			
		EndIf
	
	End Function

End Type

Type NetClient

	Field IP%

	Field playerID%
	Field playerName$ = ""
	
	Field lastReceivedTime% = 0		' our local time, generated

	Field sendPacket:NetPacket = New NetPacket	' packet we are building to send at next update
	Field lastPacket:NetPacket = New NetPacket	' last received packet from client
	
	Field sentPackets:TList     = New TList		' log from the last acked
	Field receivedPackets:TList = New TList		' log from the last acked ack
	
	' just used for stats
	
	Field bytesIn%           = 0 
	Field bytesOut%          = 0
	Field packetsIn%         = 0
	Field packetsOut%        = 0
	Field packetsOutDropped% = 0
	Field packetsInDropped%  = 0
	
End Type

Type NetPacket
	
	Field singleIP% = 0
	
	Field diffSeq:Short  = 0
	Field thisSeq:Short  = 0
	Field ackedSeq:Short = 0
	
	Field events:TList = New TList	' list of NetEvents
	
	Field controlEvent:NetControlEvent = New NetControlEvent
	Field stateEvent:NetStateEvent     = New NetStateEvent
	
	Method SendTo(client:NetClient)
	
		Network.ppmInputBuffer.SetSize(Network.ppmInputBuffer.capacity)	
		Network.ppmInputBuffer.Seek(0)

		Network.ppmInputBuffer.WriteShort(diffSeq)
		Network.ppmInputBuffer.WriteShort(thisSeq)
		Network.ppmInputBuffer.WriteShort(ackedSeq)

		Local destinationIP% = Network.broadcastIP
			
		If client

			destinationIP = client.IP
						
			Local diffPacket:NetPacket = Null
			
			For Local searchPacket:NetPacket = EachIn client.sentPackets
			
				If searchPacket.thisSeq = diffSeq
				
					diffPacket = searchPacket
					
					Exit
					
				EndIf
				
			Next
			
			If Network.netState = Network.STATE_CLIENT
			
				controlEvent.Serialize(Network.ppmInputBuffer, diffPacket)
				
			Else If Network.netState = Network.STATE_SERVER
				
				For Local myEntity:NetEntity = EachIn Game.entityList
				
					Local myEntityState:NetEntityState = New NetEntityState
					
					myEntityState.entityID = myEntity.entityID
					
					MapInsert(stateEvent.entityStates, String(myEntity.entityID), myEntityState)
					
				Next
				
				stateEvent.Serialize(Network.ppmInputBuffer, diffPacket)
				
			EndIf
			
		Else
	
			If singleIP Then destinationIP = singleIP
			
		EndIf
		
		For Local event:NetEvent = EachIn events
		
			event.Serialize(Network.ppmInputBuffer, Null)
			
		Next
		
		Network.ppmInputBuffer.SetSize(Network.ppmInputBuffer.position)
		Network.ppmInputBuffer.Seek(0)
		
		Network.ppmOutputBuffer.SetSize(Network.ppmOutputBuffer.capacity)
		Network.ppmOutputBuffer.Seek(0)
		
		Network.ppmOutputBuffer.WriteShort(Network.APP_MAGIC)
		Network.ppmOutputBuffer.WriteInt(0)
		
		PPM_Encode(Network.ppmOutputBuffer, Network.ppmInputBuffer, Network.PPM_ORDER, 1)
		
		Network.ppmOutputBuffer.SetSize(Network.ppmOutputBuffer.position)
		Network.ppmOutputBuffer.Seek(2)
		
		Network.ppmOutputBuffer.WriteInt(Network.ppmOutputBuffer.Checksum(6))
		
		Network.socket.SendTo(Network.ppmOutputBuffer.buffer, Network.ppmOutputBuffer.size, destinationIP, Network.PORT_NUM)
		
		Network.totalBytesOut   = Network.totalBytesOut + Network.ppmOutputBuffer.size
		Network.totalPacketsOut = Network.totalPacketsOut + 1
		
		If client
		
			client.bytesOut = client.bytesOut + Network.ppmOutputBuffer.size
			client.packetsOut = client.packetsOut + 1
			
		EndIf
			
	End Method
	
End Type

Type NetEvent
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	End Method
		
End Type

Type NetJoinEvent Extends NetEvent
	
	Field playerName$
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_JOIN)
		
		buffer.WriteString(playerName$)
	
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		If Network.netState = Network.STATE_SERVER And client = Null
		
			Local playerName$ = buffer.ReadString$()
								
			If Network.clientList.Count() + 1 >= Network.maxPlayers
				
				Console.ConsolePrint("Client connection rejected '" + playerName$ + "' - Game is full")
				
				Network.DisconnectClient(Null, sourceIP)

			Else
				
				client = New NetClient
				
				client.IP = sourceIP
				client.lastReceivedTime = System.frameTime
				
				If Network.clientList.Count() > 0
					
					client.playerID = NetClient(Network.clientList.Last()).playerID + 1
					
				Else
				
					client.playerID = 1
					
				EndIf
				
				client.playerName$ = playerName$
				
				Network.clientList.AddLast(client)
				Game.AddPlayer(client.playerID)
				
				Console.ConsolePrint("Player '" + playerName$ + "' has joined the game!")
				Network.SendChat("Server", "Player '" + playerName$ + "' has joined the game!")
				
				Local acceptEvent:NetServerAcceptEvent = New NetServerAcceptEvent
				acceptEvent.playerID = client.playerID
				
				Local acceptPacket:NetPacket = New NetPacket
				
				acceptPacket.singleIP = sourceIP
				acceptPacket.events.AddLast(acceptEvent)
				
				Network.broadcastPackets.AddLast(acceptPacket)
				
			EndIf
										
		EndIf		
	
	End Method
		
End Type

Type NetServerAcceptEvent Extends NetEvent
	
	Field playerID:Byte
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
		
		buffer.WriteByte(Network.EVENT_SERVER_ACCEPT)
		
		buffer.WriteByte(playerID)
		
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		If Network.netState = Network.STATE_CONNECTING And client = Null
		
			client = New NetClient
			
			client.lastReceivedTime = System.frameTime
			client.IP               = sourceIP
			
			client.playerName$ = "Server"
			client.playerID    = 0
			
			Network.clientList.AddLast(client)
			
			Console.ConsolePrint("Connected.")
			
			Network.netState = Network.STATE_CLIENT
			
		EndIf
	
	End Method
			
End Type

Type NetLeaveEvent Extends NetEvent
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_LEAVE)
		
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		If client And Network.netState = Network.STATE_SERVER
		
			Console.ConsolePrint("Client disconnected - Client closed connection")
			Console.ConsolePrintClientInfo(client)
			
			Network.DisconnectClient(client)
			
		EndIf

	End Method
		
End Type

Type NetServerClosedEvent Extends NetEvent
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_SERVER_CLOSED)
		
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		If Network.netState = Network.STATE_CLIENT And client
		
			Console.ConsolePrint("Disconnected - Server closed connection")
			
			Network.LeaveGame()
			
		Else If Network.netState = Network.STATE_CONNECTING
			
			Console.ConsolePrint("Connection denied.")
			
			Network.LeaveGame()
			
		EndIf
	
	End Method
		
End Type

Type NetAnnounceEvent Extends NetEvent
	
	Field numPlayers:Byte	 ' 0 if not hosting

	Field playerName$
	
	Field maxPlayers:Byte ' only present if hosting
	Field gameName$       ' only present if hosting	
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_ANNOUNCE)
		
		buffer.WriteByte(numPlayers)
		buffer.WriteString(playerName$)
		
		If numPlayers
		
			buffer.WriteByte(maxPlayers)
			buffer.WriteString(gameName$)
			
		EndIf
	
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		numPlayers:Byte = buffer.ReadByte()
		playerName$     = buffer.ReadString$()
		
		If numPlayers
		
			maxPlayers:Byte = buffer.ReadByte()
			gameName$       = buffer.ReadString$()

			Console.ConsolePrint("Game found:")
			Console.ConsolePrint("IP: " + DottedIP$(sourceIP))
			Console.ConsolePrint("Name: " + gameName$)
			Console.ConsolePrint("Host: " + playerName$)
			Console.ConsolePrint("Players: " + String(numPlayers) + " / " + String(maxPlayers))
			Console.ConsolePrint("")
			
		Else
		
			Console.ConsolePrint("Player found: " + playerName$)
			
		EndIf		

	End Method
	
End Type

Type NetQueryEvent Extends NetEvent
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_QUERY)
	
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		If Network.netState <> Network.STATE_CLIENT
		
			Network.SendAnnounce(sourceIP)
			
		EndIf
	
	End Method
		
End Type

Type NetChatEvent Extends NetEvent

	Field message$
	Field playerName$	' only present if coming from non-client
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_CHAT)

		buffer.WriteString(message$)
		buffer.WriteString(playerName$)

	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
	
		message$    = buffer.ReadString$()
		playerName$ = buffer.ReadString$()
		
		If client And Network.netState = Network.STATE_SERVER
		
			playerName$ = client.playerName$
			
		EndIf
		
		If Not diffPacket
		
			Console.ConsolePrint(playerName$ + " (lan): " + message$, Console.CONSOLE_CHAT_COLOR_R, Console.CONSOLE_CHAT_COLOR_G, Console.CONSOLE_CHAT_COLOR_B)
			
		Else
					
			If Network.netState = Network.STATE_SERVER
									
				Network.SendChat(playerName$, message$, False, client)
				
			Else If Network.netState = Network.STATE_CLIENT
			
				Console.ConsolePrint(playerName$ + ": " + message$, Console.CONSOLE_CHAT_COLOR_R, Console.CONSOLE_CHAT_COLOR_G, Console.CONSOLE_CHAT_COLOR_B)
				
			EndIf
						
		EndIf
										
	End Method
		
End Type

Type NetStateEvent Extends NetEvent

	Field receivedClientTime%	
	
	Field entitiesCreated:TList   = New TList	' entity IDs (shorts)
	Field entitiesDestroyed:TList = New TList	' entity IDs (shorts)
	
	Field entityStates:TMap = CreateMap() ' map from entity IDs to NetEntityStates
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_ENTITY_UPDATE)
		
		Return
		
		buffer.WriteInt(receivedClientTime)
		
		buffer.WriteShort(entitiesCreated.Count())
		buffer.WriteShort(entitiesDestroyed.Count())
		
		Local lastEntityCreatedTypeID:Short = 0
		Local lastEntityCreatedID:Short     = 0
		
		'buffer.SetDeltaSource(System.nullStream)
		
		For Local _createdID$ = EachIn entitiesCreated
			
			Local createdID:Short = _createdID$.ToInt()
			
			Local entityTypeID:Short = Game.entities[createdID].typeIndex - lastEntityCreatedTypeID
			Local entityID:Short     = createdID - lastEntityCreatedID - 1
			
			buffer.WriteShort(entityTypeID)
			buffer.WriteShort(entityID)
			
			lastEntityCreatedTypeID = entityTypeID
			lastEntityCreatedID     = entityID
		
		Next
		
		Local lastEntityDestroyedID:Short = 0
		
		For Local _destroyedID$ = EachIn entitiesDestroyed
			
			Local destroyedID:Short = _destroyedID$.ToInt()
			
			Local entityID:Short = destroyedID - lastEntityDestroyedID - 1
		
			buffer.WriteShort(entityID)
			
			lastEntityDestroyedID = entityID
			
		Next
		
		For Local myEntityState:NetEntityState = EachIn MapValues(entityStates)

			Local oldEntityState:NetEntityState = Null
			
			If diffPacket Then oldEntityState = NetEntityState(MapValueForKey(diffPacket.stateEvent.entityStates, String(myEntityState.entityID)))
			
			If oldEntityState
			
				'buffer.SetDeltaSource(oldEntityState.state)
				oldEntityState.state.Seek(0)
				
			Else
			
				'buffer.SetDeltaSource(System.nullStream)
				
			EndIf
			
			myEntityState.state = RAMStream.Create(Network.MAX_ENTITY_STATE_SIZE)
			
			Game.entities[myEntityState.entityID].HostSerializeState(buffer)
			Game.entities[myEntityState.entityID].HostSerializeState(myEntityState.state)
		
		Next		
		
		'buffer.SetDeltaSource(Null)
					
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
		
		Return
		
		If Network.netState = Network.STATE_CLIENT And client
			
			Local lastReceivedClientTime% = buffer.ReadInt()
			
			receivedClientTime = lastReceivedClientTime
			
			Local numCreatedEntities% = buffer.ReadShort()
			Local numDeletedEntities% = buffer.ReadShort()
			
			Local lastEntityCreatedTypeID:Short = 0
			Local lastEntityCreatedID:Short     = 0
			
			'buffer.SetDeltaSource(System.nullStream)
			
			For Local e% = 0 To numCreatedEntities - 1
			
				Local entityTypeID:Short = buffer.ReadShort() + lastEntityCreatedTypeID
				Local entityID:Short     = buffer.ReadShort() + lastEntityCreatedID + 1
				
				entitiesCreated.AddLast(String(entityID))
				
				Local myEntity:NetEntity = New NetEntity
				
				If (Game.entities[entityID]) Then Game.entities[entityID].Destroy()
				
				myEntity.typeIndex = entityTypeID
				myEntity.entityID  = entityID
				
				myEntity.Create()
				
				lastEntityCreatedTypeID = entityTypeID
				lastEntityCreatedID     = entityID
			
			Next
			
			Local lastEntityDestroyedID:Short = 0
			
			For Local e% = 0 To numDeletedEntities - 1
			
				Local entityID:Short = buffer.ReadShort() + lastEntityDestroyedID + 1
			
				entitiesDestroyed.AddLast(String(entityID))
				
				Game.entities[entityID].Destroy()
				
				lastEntityDestroyedID = entityID
				
			Next
			
			For Local myEntity:NetEntity = EachIn Game.entityList
	
				Local oldEntityState:NetEntityState = NetEntityState(MapValueForKey(diffPacket.stateEvent.entityStates, String(myEntity.entityID)))
				
				If oldEntityState
				
					'buffer.SetDeltaSource(oldEntityState.state)
					
				Else
				
					'buffer.SetDeltaSource(System.nullStream)
					
				EndIf
				
				Local newEntityState:NetEntityState = New NetEntityState
				
				newEntityState.entityID = myEntity.entityID
				newEntityState.state    = RAMStream.Create(Network.MAX_ENTITY_STATE_SIZE)
				
				myEntity.ClientDeserializeState(buffer)
				myEntity.HostSerializeState(newEntityState.state)
				
				MapInsert(entityStates, String(newEntityState.entityID), newEntityState)
			
			Next
			
			'buffer.SetDeltaSource(Null)
		
		EndIf
		
	End Method
		
End Type

Type NetEntityState

	Field entityID:Short
	Field state:RAMStream
	
End Type

Type NetControlEvent Extends NetEvent

	Field receivedClientTime%	
	
	Field frames:TList = New TList
	
	Method Serialize(buffer:RAMStream, diffPacket:NetPacket)
	
		buffer.WriteByte(Network.EVENT_ENTITY_CONTROL)
		
		Return
		
		buffer.WriteInt(receivedClientTime)
		
		buffer.WriteByte(frames.Count())
		
		For Local frame:NetControlFrame = EachIn frames	' todo: delta compression

			buffer.WriteShort(frame.entityID)
						
			frame.state.Seek(0)
			
			For Local b% = 0 To Frame.State.Size - 1
		
				buffer.WriteByte(frame.state.ReadByte())
				
			Next
			
		Next
			
	End Method
	
	Method Deserialize(buffer:RAMStream, client:NetClient, sourceIP%, diffPacket:NetPacket)
		
		Return
		
		If Network.netState = Network.STATE_SERVER And client <> Null
		
			receivedClientTime = buffer.ReadInt()
			
			Local numFrames% = buffer.ReadByte()
			
			For Local f% = 0 To numFrames - 1
			
				Local frame:NetControlFrame = New NetControlFrame
				
				frame.entityID = buffer.ReadShort()
				Local startPos% = buffer.position
				
				Local myPlayer:PlayerEntity = PlayerEntity(Game.entities[frame.entityID])
				
				myPlayer.HostDeserializeControl(buffer, client.playerID)				' todo: delta compression and process input for each frame
				
				frame.state = RAMStream.Create(buffer.position - startPos)
				
				For Local b% = startPos To buffer.position - 1
				
					frame.state.buffer[b - startPos] = buffer.buffer[b]
					
				Next
						
			Next
			
		EndIf
		
	End Method
		
End Type

Type NetControlFrame

	Field entityID:Short
	Field state:RAMStream
	
End Type









	
